﻿using System;
using System.Data;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace DotHandler
{
    /// <summary>
    /// Coleção de dados
    /// </summary>
    /// <typeparam name="T">Tipo nó da coleção</typeparam>
    public abstract class Colecao<T>
    {
        #region Propriedades
        protected dynamic[] _storage;

        protected Type _dataType;
        protected int[] _index;
        protected uint _size;
        private string _name;
        protected int identity = 0;

        public int Identity { get { return this.identity; } set { } }

        private dynamic[] _bag;
        #endregion

        #region Construtor
        /*****************************************************************************************/
        public Colecao(uint size)
        {
            this._bag = new dynamic[3];
            this._dataType = typeof(T);
            if (size > 0)
            {
                this._size = size;
                this._storage = (dynamic[])Array.CreateInstance(_dataType, size);
                this._name = this.GetType().Name;
            }
            else
            {
                throw new NoNullAllowedException();
            }
        }

        #endregion

        public int Size
        {
            get { return (int)this._size; }
        }

        #region Acessores
        /*****************************************************************************************/
        public dynamic Get(int index)
        {
            if (!this.IsEmpty())
            {
                dynamic n = this._storage[index];
                return n;
            }
            throw new NullReferenceException();
        }

        public dynamic[] ToArray()
        {
            return _storage;
        }

        public abstract void Each(Func<dynamic, bool> handler);
        /*****************************************************************************************/
        #endregion

        #region Controladores
        /*****************************************************************************************/
        public abstract void Destroy();
        public abstract int Quantity { get; }
        public abstract int Index { get; }
        public abstract Boolean IsFull();
        public abstract Boolean IsEmpty();
        /*****************************************************************************************/
        #endregion

        #region Persistência
        /*****************************************************************************************/
        public void Persist()
        {
            Stream stream = File.Open("./JL_" + _name + ".dhd", FileMode.Create);
            BinaryFormatter bformatter = new BinaryFormatter();
            _bag[0] = _index;
            _bag[1] = _storage;
            _bag[2] = identity;
            bformatter.Serialize(stream, _bag);
            stream.Close();
        }

        public void Load()
        {
            Stream stream = File.Open("./JL_" + _name + ".dhd", FileMode.Open);
            BinaryFormatter bformatter = new BinaryFormatter();
            try
            {
                _bag = (dynamic[])bformatter.Deserialize(stream);
                _index = _bag[0];
                _storage = _bag[1];
                identity = _bag[2];
            }catch(Exception e)
            {
                Console.Write(e.Message);
            }
            stream.Close();
        }
        /*****************************************************************************************/
        #endregion 
       
        #region Ordenação e Procura
        /*****************************************************************************************/
        /// <summary>
        /// Responsável mestre pela ordenação do array genérico
        /// </summary>
        /// <param name="nodes">Entrada de array</param>
        /// <param name="length">Quantidade de membros ativos no array</param>
        /// <param name="rule">Propriedade que será utilizada para ordenação</param>
        /// <returns>Array ordenado</returns>
        private dynamic[] MergeSort(dynamic[] nodes, int length, dynamic rule = null)
        {
            if (length <= 1||nodes==null)
                return nodes;
            int middle = length / 2;
            dynamic[] left = new dynamic[middle];
            for (int i = 0; i < middle; i++)
            {
                left[i] = nodes[i];
            }
            dynamic[] right = new dynamic[length - middle];
            for (int i = 0; i < length - middle; i++)
            {
                right[i] = nodes[i + middle];
            }
            left = MergeSort(left, left.Length, rule);
            right = MergeSort(right, right.Length, rule);

            int leftptr = 0;
            int rightptr = 0;

            dynamic[] sorted = new dynamic[length];
            for (int k = 0; k < length; k++)
            {
                var nodesRight = right;
                var nodesLeft = left;
                if (rightptr == right.Length || ((leftptr < left.Length) && left[leftptr].ToString(rule).CompareTo(right[rightptr].ToString(rule)) <= 0))
                {
                    sorted[k] = left[leftptr];
                    leftptr++;
                }
                else if (leftptr == left.Length || ((rightptr < right.Length) && right[rightptr].ToString(rule).CompareTo(left[leftptr].ToString(rule)) <= 0))
                {
                    sorted[k] = right[rightptr];
                    rightptr++;
                }
            }
            return sorted;
        }

        /// <summary>
        /// Faz a chamada para a ordenação do array principal
        /// </summary>
        /// <param name="property">Propriedade que será utilizada como chave</param>
        public void Sort(string property = null)
        {
            var sorted = this.MergeSort(_storage, Quantity, property);
            for(var l = 0;l<sorted.Length;l+=1){
                _storage[l] = sorted[l];
            }   
        }

        /// <summary>
        /// Algorítmo de busca binária conforme propriedade
        /// Sempre utiliza Strings para comparação
        /// </summary>
        /// <param name="property">Propriedade a ser comparada</param>
        /// <param name="value"></param>
        /// <returns></returns>
        public T Find(String property, String value)
        {
            if (Quantity <= 0)
            {
                return default(T);
            }
            value = value.ToLower();
            this.Sort(property);

            int middle;
            int min = 0;
            int max = Quantity - 1;

            do
            {
                middle = (int)(min + max) / 2;
                var prop = _storage[middle].ToString(property);
                var comparable = prop.CompareTo(value);
                if (comparable == 0)
                {
                    return _storage[(middle%this.Size)];
                }
                else if (comparable < 0)
                {
                    min = middle + 1;
                }
                else
                {
                    max = middle - 1;
                }
            }
            while (min <= max);

            return default(T);
        }

        public Lista<T> Find(bool multiple, String property, String value)
        {
            if (Quantity <= 0)
            {
                return null;
            }
            value = value.ToLower();
            this.Sort(property);

            int min = 0;
            int max = Quantity - 1;

            do
            {
                var minComparable = _storage[min].ToString(property).CompareTo(value);
                var maxComparable = _storage[max].ToString(property).CompareTo(value);

                if (minComparable==0 && maxComparable==0)
                {
                    int size = max - min + 1;
                    Lista<T> resultset = new Lista<T>((uint)size);
                    for (var i = min; i <= max; i += 1)
                    {
                        resultset.Add(_storage[i]);
                    }
                    return resultset;
                }

                else if (min > max)
                {
                    break;
                }

                if (minComparable != 0)
                {
                    min += 1;
                }
                if (maxComparable != 0)
                {

                    max -= 1;
                }
            }
            while (min <= max);

            return null;
        }
        /*****************************************************************************************/
        #endregion

        #region Mapeamento e Conexão
        /*****************************************************************************************/
        public dynamic Join(Type colecaoReferer, Type nodeReferer, dynamic target, dynamic property, dynamic on)
        {
            dynamic joint = Activator.CreateInstance(colecaoReferer);
            this.Each((o) =>
            {
                var value = o.GetType().GetProperty(property).GetValue(o, null);
                var t = target.Find(on, value.ToString());
                joint.Sync(nodeReferer, ref o, ref t);
                return true;
            });
            return joint;
        }

        public dynamic[] MapTo(Type target)
        {
            var s = this._storage;
            if (Quantity == 0)
                return null;
            dynamic[] map = (dynamic[])Array.CreateInstance(target, Quantity);
            int index = 0;
            Each((o) =>
            {
                Mapping m = (Mapping)Activator.CreateInstance(target);
                map[index] = m.Map(ref o);
                index += 1;
                return true;
            });
            return map;
        }
        public void MapTo(Type target, dynamic [] space)
        {
            if (Quantity == 0)
                return;
            int index = 0;
            Each((o) =>
            {
                Mapping m = (Mapping)Activator.CreateInstance(target);
                space[index] = m.Map(ref o);
                index += 1;
                return true;
            });
        }
        /*****************************************************************************************/
        #endregion
    }
}
